package cn.zjutleo.village_server.service;

import cn.hutool.json.JSONObject;
import cn.zjutleo.village_server.config.JwtConfig;
import cn.zjutleo.village_server.config.WechatConfig;
import cn.zjutleo.village_server.constants.OauthTypes;
import cn.zjutleo.village_server.constants.RedisConstants;
import cn.zjutleo.village_server.constants.RoleConstants;
import cn.zjutleo.village_server.dto.*;
import cn.zjutleo.village_server.entity.Oauth;
import cn.zjutleo.village_server.entity.User;
import cn.zjutleo.village_server.exception.apiException.AuthorizationException;
import cn.zjutleo.village_server.exception.apiException.authenticationException.AccountNotFoundException;
import cn.zjutleo.village_server.exception.apiException.authenticationException.InvalidPasswordException;
import cn.zjutleo.village_server.exception.apiException.daoException.SelectException;
import cn.zjutleo.village_server.exception.apiException.decryptException.DecryptNullException;
import cn.zjutleo.village_server.exception.apiException.smsException.MessageCheckException;
import cn.zjutleo.village_server.util.*;
import cn.zjutleo.village_server.vo.LoginInfoVo;
import cn.zjutleo.village_server.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;

/**
 * @author : Qin Zhenghan
 * @date : Created in 2021/8/15
 * @description: 授权鉴权服务类
 */
@Slf4j
@Service
public class AuthService {
    @Resource
    private UserService userService;
    @Resource
    private OauthService oauthService;
    @Resource
    private RedisService redisService;
    @Resource
    private JwtConfig jwtConfig;
    @Resource
    private WechatConfig wechatConfig;
    @Resource
    private HttpServletRequest request;
    @Resource
    private SmsService smsService;

    public LoginInfoVo loginByWechat(WechatLoginDto wechatLoginDto) throws Exception {
        WechatDto wechatDto = WechatUtil.getWxInfo(wechatLoginDto.getCode(), wechatConfig.getAppid(), wechatConfig.getSecret());
        String openid = wechatDto.getOpenid();
        if (openid == null) {
            throw new AuthorizationException("非法授权码！");
        }
        // 登录逻辑
        Oauth wechatOauth = oauthService.getOauthByOauthTypeAndOauthId(OauthTypes.WECHAT, openid);
        if (wechatOauth != null) {
            return executeLoginByUserId(wechatOauth.getUserId());
        } else {
            User user = new User();
            user.setNickname(wechatLoginDto.getNickname());
            user.setAvatarUrl(wechatLoginDto.getAvatarUrl());
            user.setRoleId(RoleConstants.ROLE_USER);
            user.setVillageId(0);
            user.setSignedNotice(true);
            user.setCreateTime(new Timestamp(System.currentTimeMillis()));
            userService.saveUser(user);
            Oauth oauthByWechat = new Oauth();
            oauthByWechat.setUserId(user.getId());
            oauthByWechat.setOauthType(OauthTypes.WECHAT);
            oauthByWechat.setOauthId(openid);
            oauthService.saveOauth(oauthByWechat);
            return executeLoginByUserId(user.getId());
        }
    }

    /**
     * 处理绑定手机号请求
     *
     * @param phoneBindDto 绑定手机号信息
     * @param userId       用户id
     * @throws AuthorizationException 授权异常
     * @throws MessageCheckException  短信校验异常
     */
    public void bindPhone(PhoneBindDto phoneBindDto, Integer userId) throws AuthorizationException, MessageCheckException, SelectException {
        boolean smsVerifyResult = smsService.verifyCode(phoneBindDto.getPhone(), phoneBindDto.getCode());
        if (!smsVerifyResult) {
            throw new MessageCheckException();
        }
        if (oauthService.getOauthByOauthTypeAndUserId(OauthTypes.PHONE, userId) != null) {
            throw new AuthorizationException("您已绑定过手机号！");
        }
        if (oauthService.getOauthByOauthTypeAndOauthId(OauthTypes.PHONE, phoneBindDto.getPhone()) != null) {
            throw new AuthorizationException("该手机号已被绑定过！");
        }
        User user = userService.getExistUserDetailById(userId);
        user.setPhone(phoneBindDto.getPhone());
        Oauth oauth = new Oauth();
        oauth.setUserId(userId);
        oauth.setOauthType(OauthTypes.PHONE);
        oauth.setOauthId(phoneBindDto.getPhone());
        oauthService.saveOauth(oauth);
    }

    /**
     * 传入手机号+密码完成登录校验并获取令牌
     *
     * @param adminLoginDto 登录信息实体类
     * @return token令牌和用户部分信息
     * @throws Exception 向上抛出异常
     */
    public LoginInfoVo loginByPhoneAndPassword(AdminLoginDto adminLoginDto) throws Exception {
        Oauth oauth = oauthService.getOauthByOauthTypeAndOauthId(OauthTypes.PHONE, adminLoginDto.getPhone());
        if (oauth == null) {
            throw new AccountNotFoundException("用户不存在！");
        }
        UserVo userVo = userService.getExistUserVoById(oauth.getUserId());
        if (EncryptUtil.getSha256(adminLoginDto.getPassword()).equals(userVo.getPassword())) {
            return executeLoginByUserId(userVo.getId());
        } else {
            throw new InvalidPasswordException("用户名或密码错误！");
        }
    }

    /**
     * 检验用户是否绑定手机号
     *
     * @param userId 用户id
     * @return 是否绑定手机号
     */
    public Boolean checkPhoneBind(Integer userId) {
        boolean checkStatus = false;
        Oauth oauth = oauthService.getOauthByOauthTypeAndUserId(OauthTypes.PHONE, userId);
        if (oauth != null) {
            checkStatus = true;
        }
        return checkStatus;
    }

    /**
     * 处理登录过程
     *
     * @param userId 用户id
     * @return token和部分用户信息
     * @throws SelectException 用户未找到异常
     */
    private LoginInfoVo executeLoginByUserId(int userId) throws SelectException {
        UserVo userVo = userService.getExistUserVoById(userId);
        String token = signToken(userVo);
        MDC.put("userId", userVo.getId().toString());
        MDC.put("ip", IpUtil.getIpAddr(request));
        return new LoginInfoVo(token, userVo);
    }

    /**
     * 签署token令牌
     *
     * @param userVo 用户私有信息封装
     * @return token令牌
     */
    private String signToken(UserVo userVo) {
        JwtPayloadDto jwtPayloadDto = JwtUtil.packagePayload(userVo);
        String token = JwtUtil.sign(jwtPayloadDto, jwtConfig.getSecret(), jwtConfig.getExpireTime());
        redisService.set(RedisConstants.REFRESH_TOKEN_PREFIX + userVo.getId(), token, jwtConfig.getRefreshTokenExpireTime());
        return token;
    }

    /**
     * 解密微信绑定手机号
     *
     * @param encryptedData 待解密数据
     * @param sessionKey 密钥
     * @param iv 加密算法初始向量
     * @return 解密手机号
     * @throws DecryptNullException 解密为空异常
     */
    private String decryptPhone(String encryptedData, String sessionKey, String iv) throws DecryptNullException {
        log.info("encryptedData: " + encryptedData);
        log.info("sessionKey: " + sessionKey);
        log.info("iv: " + iv);
        JSONObject userDecryptedData = AesDecryptUtil.decrypt(encryptedData, sessionKey, iv);
        String phone = null;
        String field = "phoneNumber";
        if (StringUtils.isNotBlank((String) userDecryptedData.get(field))) {
            phone = (String) userDecryptedData.get(field);
        } else {
            throw new DecryptNullException();
        }
        return phone;
    }

}
